using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Drill.Extensibility;

namespace Drill.Core
{
	/// <summary>
	/// Implementation of <see cref="IDependencyResolver"/> that resolves dependencies  
	/// using an ordered list of configured DrillBits.
	/// </summary>
	public class DrillChuckDependencyResolver : IDrillChuckDependencyResolver
	{
		protected enum Operation
		{
			GetInstance = 0,
			GetAllInstances
		}

		private readonly string _name;
		private readonly List<IDrillBit> _drillBits;
		private readonly IDrillBit _singleDrillBit;
		private readonly ConcurrentDictionary<string, IDrillBit> _drillBitMap;

		public DrillChuckDependencyResolver(IDependencyResolverInitContext initContext)
		{
			if (null == initContext)
			{
				throw new ArgumentNullException("initContext");
			}

			if (null == initContext.DrillBits || initContext.DrillBits.Count < 1)
			{
				throw new ArgumentException(
					string.Format(
						"The initContext.DrillBits property is null or empty. "
					  + "The {0} class requires at least one configured DrillBit."
					  , GetType().FullName
						)
				  , "initContext"
					);
			}

			_name = initContext.Name;
			_drillBits = initContext.DrillBits.ToList();
			_singleDrillBit = (_drillBits.Count == 1) ? _drillBits[0] : null;
			_drillBitMap = (_drillBits.Count > 1) ? new ConcurrentDictionary<string, IDrillBit>() : null;
		}

		#region " IDependencyResolver Interface Implementation "

		/// <summary>
		/// Gets an instance of the type specified by <paramref name="targetType"/>.
		/// </summary>
		/// <param name="targetType">The type of object to be retrieved.</param>
		/// <returns>An instance of the requested type.</returns>
		public virtual object GetInstance(Type targetType)
		{
			CheckDisposed();
			object instance = null;
			if (null != _singleDrillBit)
			{
				try
				{
					instance = _singleDrillBit.GetInstance(targetType);
				}
				catch
				{
					instance = null;
				}
			}
			else
			{
				IDrillBit mappedDrillBit;
				string key = CreateMappedDrillBitKey(targetType, Operation.GetInstance);
				if (_drillBitMap.TryGetValue(key, out mappedDrillBit))
				{
					instance = mappedDrillBit.GetInstance(targetType);
				}
				else
				{
					foreach (IDrillBit drillBit in _drillBits)
					{
						try
						{
							instance = drillBit.GetInstance(targetType);
						}
						catch
						{
							instance = null;
						}

						if (null != instance)
						{
							_drillBitMap.TryAdd(key, drillBit);
							break;
						}
					}
				}
			}
			return instance;
		}

		/// <summary>
		/// Gets a named instance of the type specified by <paramref name="targetType"/>.
		/// </summary>
		/// <param name="targetType">The type of object to be retrieved.</param>
		/// <param name="name">The name of the specific object instance.</param>
		/// <returns>An instance of the requested named type.</returns>
		public virtual object GetInstance(Type targetType, string name)
		{
			CheckDisposed();
			object instance = null;
			if (null != _singleDrillBit)
			{
				try
				{
					instance = _singleDrillBit.GetInstance(targetType, name);
				}
				catch
				{
					instance = null;
				}
			}
			else
			{
				IDrillBit mappedDrillBit;
				string key = CreateMappedDrillBitKey(targetType, Operation.GetInstance, name);
				if (_drillBitMap.TryGetValue(key, out mappedDrillBit))
				{
					instance = mappedDrillBit.GetInstance(targetType, name);
				}
				else
				{
					foreach (IDrillBit drillBit in _drillBits)
					{
						try
						{
							instance = drillBit.GetInstance(targetType, name);
						}
						catch
						{
							instance = null;
						}

						if (null != instance)
						{
							_drillBitMap.TryAdd(key, drillBit);
							break;
						}
					}	
				}
			}
			return instance;
		}

		/// <summary>
		/// Get all instances of the type specified by <paramref name="targetType"/>.
		/// </summary>
		/// <param name="targetType">The type of object to be retrieved.</param>
		/// <returns>
		/// An <see cref="IEnumerable"/> instance representing all 
		/// available instances of the requested type.
		/// </returns>
		public virtual IEnumerable GetAllInstances(Type targetType)
		{
			CheckDisposed();
			IEnumerable instance = null;
			if (null != _singleDrillBit)
			{
				try
				{
					instance = _singleDrillBit.GetAllInstances(targetType);
				}
				catch
				{
					instance = null;
				}
			}
			else
			{
				IDrillBit mappedDrillBit;
				string key = CreateMappedDrillBitKey(targetType, Operation.GetAllInstances);
				if (_drillBitMap.TryGetValue(key, out mappedDrillBit))
				{
					instance = mappedDrillBit.GetAllInstances(targetType);
				}
				else
				{
					foreach (IDrillBit drillBit in _drillBits)
					{
						try
						{
							instance = drillBit.GetAllInstances(targetType);
						}
						catch
						{
							instance = null;
						}

						if (null != instance)
						{
							_drillBitMap.TryAdd(key, drillBit);
							break;
						}
					}
				}
			}
			return instance;
		}

		/// <summary>
		/// Gets an instance of the type specified by <typeparamref name="TTargetType"/>.
		/// </summary>
		/// <typeparam name="TTargetType">The type of object to be retrieved.</typeparam>
		/// <returns>An instance of the requested type.</returns>
		public virtual TTargetType GetInstance<TTargetType>()
		{
			CheckDisposed();
			TTargetType instance = default(TTargetType);
			if (null != _singleDrillBit)
			{
				try
				{
					instance = _singleDrillBit.GetInstance<TTargetType>();
				}
				catch
				{
					instance = default(TTargetType);
				}
			}
			else
			{
				IDrillBit mappedDrillBit;
				string key = CreateMappedDrillBitKey(typeof(TTargetType), Operation.GetInstance);
				if (_drillBitMap.TryGetValue(key, out mappedDrillBit))
				{
					instance = mappedDrillBit.GetInstance<TTargetType>();
				}
				else
				{
					foreach (IDrillBit drillBit in _drillBits)
					{
						try
						{
							instance = drillBit.GetInstance<TTargetType>();
						}
						catch
						{
							instance = default(TTargetType);
						}

						if (false == Equals(instance, default(TTargetType)))
						{
							_drillBitMap.TryAdd(key, drillBit);
							break;
						}
					}
				}
			}
			return instance;
		}

		/// <summary>
		/// Gets a named instance of the type specified by <typeparamref name="TTargetType"/>.
		/// </summary>
		/// <typeparam name="TTargetType">The type of object to be retrieved.</typeparam>
		/// <param name="name">Name the object was registered with.</param>
		/// <returns>An instance of the requested named type.</returns>
		public virtual TTargetType GetInstance<TTargetType>(string name)
		{
			CheckDisposed();
			TTargetType instance = default(TTargetType);
			if (null != _singleDrillBit)
			{
				try
				{
					instance = _singleDrillBit.GetInstance<TTargetType>(name);
				}
				catch
				{
					instance = default(TTargetType);
				}
			}
			else
			{
				IDrillBit mappedDrillBit;
				string key = CreateMappedDrillBitKey(typeof(TTargetType), Operation.GetInstance, name);
				if (_drillBitMap.TryGetValue(key, out mappedDrillBit))
				{
					instance = mappedDrillBit.GetInstance<TTargetType>(name);
				}
				else
				{
					foreach (IDrillBit drillBit in _drillBits)
					{
						try
						{
							instance = drillBit.GetInstance<TTargetType>(name);
						}
						catch
						{
							instance = default(TTargetType);
						}

						if (false == Equals(instance, default(TTargetType)))
						{
							_drillBitMap.TryAdd(key, drillBit);
							break;
						}
					}
				}
			}
			return instance;
		}

		/// <summary>
		/// Get all instances of the type specified by <typeparamref name="TTargetType"/>.
		/// </summary>
		/// <typeparam name="TTargetType">The type of object to be retrieved.</typeparam>
		/// <returns>
		/// An <see cref="IEnumerable{TTargetType}"/> instance representing all 
		/// available instances of the requested type.
		/// </returns>
		public virtual IEnumerable<TTargetType> GetAllInstances<TTargetType>()
		{
			CheckDisposed();
			IEnumerable<TTargetType> instance = null;
			if (null != _singleDrillBit)
			{
				try
				{
					instance = _singleDrillBit.GetAllInstances<TTargetType>();
				}
				catch
				{
					instance = null;
				}
			}
			else
			{
				IDrillBit mappedDrillBit;
				string key = CreateMappedDrillBitKey(typeof(TTargetType), Operation.GetAllInstances);
				if (_drillBitMap.TryGetValue(key, out mappedDrillBit))
				{
					instance = mappedDrillBit.GetAllInstances<TTargetType>();
				}
				else
				{
					foreach (IDrillBit drillBit in _drillBits)
					{
						try
						{
							instance = drillBit.GetAllInstances<TTargetType>();
						}
						catch
						{
							instance = null;
						}

						if (null != instance)
						{
							_drillBitMap.TryAdd(key, drillBit);
							break;
						}
					}
				}
			}
			return instance;
		}

		/// <summary>
		/// Gets the underlying DrillBits.
		/// </summary>
		/// <value>The underlying DrillBits.</value>
		public virtual IList<IDrillBit> DrillBits
		{
			get
			{
				CheckDisposed();
				return _drillBits;
			}
		}

		private static string CreateMappedDrillBitKey(Type type, Operation operation, string name = null)
		{
			StringBuilder sb = new StringBuilder(1000);
			sb.Append(operation);
			if (false == string.IsNullOrWhiteSpace(name))
			{
				sb.Append("[");
				sb.Append(name);
				sb.Append("]");
			}
			sb.Append(":");
			sb.Append(type.AssemblyQualifiedName);
			return sb.ToString();
		}

		#endregion " IDependencyResolver Interface Implementation "

		#region " IDisposable Interface and Disposable Pattern Implementation "

		/// <summary>
		/// Gets a value indicating whether this instance is disposed.
		/// </summary>
		/// <value><c>true</c> if this instance is disposed; otherwise, <c>false</c>.</value>
		public bool IsDisposed { get; private set; }

		/// <summary>
		/// Performs managed and unmanaged resource cleanup.
		/// </summary>
		public void Dispose()
		{
			// NOTE: The Dispose() method is public.
			// NOTE: The Dispose() method is not virtual because it should not be overridden.

			if (false == IsDisposed)
			{
				Dispose(true);
				GC.SuppressFinalize(this);
			}
		}

		/// <summary>
		/// Performs managed and unmanaged resource cleanup.
		/// </summary>
		/// <param name="disposing"><c>true</c> if disposing, <c>false</c> if finalizing.</param>
		private void Dispose(bool disposing)
		{
			// NOTE: The Dispose(bool disposing) method is private.
			// NOTE: The Dispose(bool disposing) method is not virtual (because it is private).

			// When the operation is Dispose, call DisposeManagedResources AND DisposeUnmanagedResources.
			// When the operation is Finalize, only call DisposeUnmanagedResources.

			if (false == IsDisposed)
			{
				if (disposing)
				{
					DisposeManagedResources();
				}

				DisposeUnmanagedResources();

				// Set the _isDisposed flag.
				IsDisposed = true;
			}
		}

		protected virtual void DisposeManagedResources()
		{
			// NOTE: The DisposeManagedResources() method is protected.
			// NOTE: The DisposeManagedResources() method is virtual.

			// Clean up managed resources here.
			// * Call Dispose on all disposable object references
			// * Unsubscribe from all subscribed events
			// * Set managed object references to null

			// Dispose the DrillBits.
			if (_drillBits.Count > 0)
			{
				_drillBits.ForEach(item => item.Dispose());
				_drillBits.Clear();
			}
		}

		protected virtual void DisposeUnmanagedResources()
		{
			// NOTE: The DisposeUnmanagedResources() method is protected.
			// NOTE: The DisposeUnmanagedResources() method is virtual.

			// Clean up unmanaged resources here.
			// * Release handles
			// * Free allocated memory
			// * Dereference COM objects
			// * Set large fields to null
		}

		private void CheckDisposed()
		{
			if (IsDisposed)
			{
				string objectName = string.Format("{0}[\"{1}\"]", GetType().Name, _name);

				throw new ObjectDisposedException(objectName, string.Format("{0} has been disposed.", objectName));
			}
		}

		#endregion " IDisposable Interface and Disposable Pattern Implementation "
	}
}